I ve had a kludgy mess of electronic address books for most of two decades,
and have got rather fed up with it. My stack consisted of:
~/.mutt/aliases
, a flat text file consisting of mutt
alias
commands
- lbdb configuration to query
~/.mutt/aliases
, Debian s LDAP database, and Canonical s LDAP database,
so that I can search by name with Ctrl-t in mutt
when composing a new message
- Google Contacts, which I used from Android and was completely separate
from all of the above
The biggest practical problem with this was that I had the address book that
was most convenient for me to add things to (Google Contacts) and the one I
used when sending email, and no sensible way to merge them or move things
between them. I also wasn t especially comfortable with having all my
contact information in a proprietary web service.
My goals for a replacement address book system were:
- free software throughout
- storage under my control
- single common database
- minimal manual transcription when consolidating existing databases
- integration with Android such that I can continue using the same
contacts, messaging, etc. apps
- integration with
mutt
such that I can continue using the same query interface
- not having to write my own software, because honestly
I think I have all this now!
New stack
The obvious basic technology to use is
CardDAV: it s fairly complex,
admittedly, but lots of software supports it and one of my goals was not
having to write my own thing. This meant I needed a CardDAV server, some
way to sync the database to and from both Android and the system where I run
mutt
, and whatever query glue was necessary to get
mutt
to understand vCards.
There are lots of different alternatives here, and if anything the problem
was an embarrassment of choice. In the end I just decided to go for things
that looked roughly the right shape for me and tried not to spend too much
time in analysis paralysis.
CardDAV server
I went with
Xandikos for the
server, largely because I know Jelmer and have generally had pretty good
experiences with their software, but also because using Git for history of
the backend storage seems like something my future self will thank me for.
It isn t packaged in stretch, but it s in Debian unstable, so I installed it
from there.
Rather than the standalone mode suggested on the web page, I decided to set
it up in what felt like a more robust way using
WSGI. I installed
uwsgi
,
uwsgi-plugin-python3
, and
libapache2-mod-proxy-uwsgi
, and created the
following file in
/etc/uwsgi/apps-available/xandikos.ini
which I then
symlinked into
/etc/uwsgi/apps-enabled/xandikos.ini
:
[uwsgi]
socket = 127.0.0.1:8801
uid = xandikos
gid = xandikos
umask = 022
master = true
cheaper = 2
processes = 4
plugin = python3
module = xandikos.wsgi:app
env = XANDIKOSPATH=/srv/xandikos/collections
The port number was arbitrary, as was the path. You need to create the
xandikos
user and group first (
adduser --system --group --no-create-home
--disabled-login xandikos
). I created
/srv/xandikos
owned by
xandikos:xandikos
and mode 0700, and I recommend
setting a
umask as shown above since uwsgi s default
umask is 000 (!). You should also run
sudo -u xandikos xandikos -d
/srv/xandikos/collections --autocreate
and then Ctrl-c it after a short
time (I think it would be nicer if there were a way to
ask the WSGI wrapper
to do this).
For Apache setup, I kept it reasonably simple: I ran
a2enmod proxy_uwsgi
,
used
htpasswd
to create
/etc/apache2/xandikos.passwd
with a username and
password for myself, added a virtual host in
/etc/apache2/sites-available/xandikos.conf
, and enabled it with
a2ensite
xandikos
:
<VirtualHost *:443>
ServerName xandikos.example.org
ServerAdmin me@example.org
ErrorLog /var/log/apache2/xandikos-error.log
TransferLog /var/log/apache2/xandikos-access.log
<Location />
ProxyPass "uwsgi://127.0.0.1:8801/"
AuthType Basic
AuthName "Xandikos"
AuthBasicProvider file
AuthUserFile "/etc/apache2/xandikos.passwd"
Require valid-user
</Location>
</VirtualHost>
Then
service apache2 reload
, set the new virtual host up with
Let s
Encrypt, reloaded again, and off we go.
Android integration
I installed
DAVdroid from the Play Store: it
cost a few pounds, but I was
OK with that since it s GPLv3 and I m happy to
help fund free software. I created two accounts, one for my existing Google
Contacts database (and in fact calendaring as well, although I don t intend
to switch over to self-hosting that just yet), and one for the new Xandikos
instance. The
Google
setup was a bit fiddly
because I have two-step verification turned on so I had to create an
app-specific password. The Xandikos setup was straightforward: base
URL,
username, password, and done.
Since I didn t completely trust the new setup yet, I followed what seemed
like the most robust option from the
DAVdroid contacts syncing
documentation,
and used the stock contacts app to export my Google Contacts account to a
.vcf
file and then import that into the appropriate DAVdroid account
(which showed up automatically). This seemed straightforward and everything
got pushed to Xandikos. There are some weird delays in syncing contacts
that I don t entirely understand, but it all seems to get there in the end.
mutt integration
First off I needed to sync the contacts. (In fact I happen to run
mutt
on
the same system where I run Xandikos at the moment, but I don t want to rely
on that, and going through the CardDAV server means that I don t have to
poke holes for myself using filesystem permissions.) I used
vdirsyncer for this. In
~/.vdirsyncer/config
:
[general]
status_path = "~/.vdirsyncer/status/"
[pair contacts]
a = "contacts_local"
b = "contacts_remote"
collections = ["from a", "from b"]
[storage contacts_local]
type = "filesystem"
path = "~/.contacts/"
fileext = ".vcf"
[storage contacts_remote]
type = "carddav"
url = "<Xandikos base URL>"
username = "<my username>"
password = "<my password>"
Running
vdirsyncer discover
and
vdirsyncer sync
then synced everything
into
~/.contacts/
. I added an hourly
crontab
entry to run
vdirsyncer
-v WARNING sync
.
Next, I needed a command-line address book tool based on this.
khard looked about right and is in
stretch, so I installed that. In
~/.config/khard/khard.conf
(this is
mostly just the example configuration, but I preferred to sort by first name
since not all my contacts have neat first/last names):
[addressbooks]
[[contacts]]
path = ~/.contacts/<UUID of my contacts collection>/
[general]
debug = no
default_action = list
editor = vim
merge_editor = vimdiff
[contact table]
# display names by first or last name: first_name / last_name
display = first_name
# group by address book: yes / no
group_by_addressbook = no
# reverse table ordering: yes / no
reverse = no
# append nicknames to name column: yes / no
show_nicknames = no
# show uid table column: yes / no
show_uids = yes
# sort by first or last name: first_name / last_name
sort = first_name
[vcard]
# extend contacts with your own private objects
# these objects are stored with a leading "X-" before the object name in the vcard files
# every object label may only contain letters, digits and the - character
# example:
# private_objects = Jabber, Skype, Twitter
private_objects = Jabber, Skype, Twitter
# preferred vcard version: 3.0 / 4.0
preferred_version = 3.0
# Look into source vcf files to speed up search queries: yes / no
search_in_source_files = no
# skip unparsable vcard files: yes / no
skip_unparsable = no
Now
khard list
shows all my contacts. So far so good. Apparently there
are some
awkward vCard compatibility
issues with creating or modifying
contacts from the
khard
end. I ve tried adding one address from
~/.mutt/aliases
using
khard
and it seems to at least minimally work for
me, but I haven t explored this very much yet.
I had to install python3-vobject 0.9.4.1-1 from experimental to fix
eventable/vobject#39
saving certain vCard files.
Finally,
mutt
integration. I already had
set query_command="lbdbq '%s'"
in
~/.muttrc
, and I wanted to keep that in place since I still wanted to
use
LDAP querying as well. I had to write a very small amount of code for
this (perhaps I should contribute this to
lbdb
upstream?), in
~/.lbdb/modules/m_khard
:
#! /bin/sh
m_khard_query ()
khard email --parsable --remove-first-line --search-in-source-files "$1"
My full
~/.lbdb/rc
now reads as follows (you probably won t want the
LDAP
stuff, but I ve included it here for completeness):
MODULES_PATH="$MODULES_PATH $HOME/.lbdb/modules"
METHODS='m_muttalias m_khard m_ldap'
LDAP_NICKS='debian canonical'
Next steps
I ve deleted one account from Google Contacts just to make sure that
everything still works (e.g. I can still search for it when composing a new
message), but I haven t yet deleted everything. I won t be adding anything
new there though.
I need to push everything from
~/.mutt/aliases
into the new system. This
is only about 30 contacts so shouldn t take too long.
Overall this feels like a big improvement! It wasn t a trivial amount of
setup for just me, but it means I have both better usability for myself and
more independence from proprietary services, and I think I can add extra
users with much less effort if I need to.
Postscript
A day later and I ve consolidated all my accounts from Google Contacts and
~/.mutt/aliases
into the new system, with the exception of one group that
I had defined as a
mutt
alias and need to work out what to do with. This
all went smoothly.
I ve filed the new
lbdb
module as
#866178, and the
python3-vobject
bug as
#866181.